#include "SpaceFortress.h"
#include "ComodityManager.h"

#include <assert.h>

ComodityManager::ComodityManager()
{
	_data.size = 0;
	_data.capacity = 0;
	_data.buffer = NULL;

	//_data.entity = NULL;
	_data.comodity_hash = NULL;
	_data.registered_buyers = NULL;
	_data.registered_sellers = NULL;

	_data.previuos_turns = NULL;
	_data.mean = NULL;

	_data.supply = NULL;
	_data.demand = NULL;
}

ComodityManager::~ComodityManager()
{
	if (_data.capacity) {
		delete[] _data.buffer;
	}
	_data.buffer = NULL;

	//_data.entity = NULL;
	_data.comodity_hash = NULL;
	_data.registered_buyers = NULL;
	_data.registered_sellers = NULL;

	_data.previuos_turns = NULL;
	_data.mean = NULL;

	_data.supply = NULL;
	_data.demand = NULL;
}

void ComodityManager::Allocate(const uint32 capacity)
{
	assert(capacity > _data.capacity);

	InstanceData new_data;
	const uint32 bytes = capacity * (3 * sizeof(uint32) + sizeof(PriceTracking) + 3 * sizeof(float));
	new_data.buffer = new unsigned char[bytes];
	new_data.size = _data.size;
	new_data.capacity = capacity;

	//new_data.entity = (Entity*)(new_data.buffer);
	new_data.comodity_hash = (uint32*)(new_data.buffer);
	new_data.registered_buyers = (uint32*)(new_data.comodity_hash + capacity);
	new_data.registered_sellers = (uint32*)(new_data.registered_buyers + capacity);
	new_data.previuos_turns = (PriceTracking*)(new_data.registered_sellers + capacity);
	new_data.mean = (float*)(new_data.previuos_turns + 16 * capacity);
	new_data.supply = (float*)(new_data.mean + capacity);
	new_data.demand = (float*)(new_data.supply + capacity);

	//memcpy(new_data.entity, _input_data.entity, _data.size * sizeof(Entity));
	memcpy(new_data.comodity_hash, _data.comodity_hash, _data.size * sizeof(uint32));
	memcpy(new_data.registered_buyers, _data.registered_buyers, _data.size * sizeof(uint32));
	memcpy(new_data.registered_sellers, _data.registered_sellers, _data.size * sizeof(uint32));
	memcpy(new_data.previuos_turns, _data.previuos_turns, _data.size * sizeof(PriceTracking));
	memcpy(new_data.mean, _data.mean, _data.size * sizeof(float));
	memcpy(new_data.supply, _data.supply, _data.size * sizeof(float));
	memcpy(new_data.demand, _data.demand, _data.size * sizeof(float));

	delete[] _data.buffer;
	_data = new_data;
}

void ComodityManager::Initialize(const OpenVector<uint32>& comodities)
{
	Allocate(comodities.size);

	for (uint32 i = 0; i < comodities.size; i++) {
		_data.comodity_hash[i] = comodities.data[i];
	}
}

void ComodityManager::InitializeBuyData(BidData* bid_data) const
{
	bid_data = new BidData[_data.size];
	for (uint32 i = 0; i < _data.size; i++) {
		const uint32 size = _data.registered_buyers[i];
		const uint32 mean = _data.mean[i];
		bid_data[i].Initialize(size);
		for (uint32 j = 0; j < size; j++) {
			bid_data[i].price[j] = mean;
		}
	}
}

void ComodityManager::InitializeSellData(BidData* bid_data) const
{
	bid_data = new BidData[_data.size];
	for (uint32 i = 0; i < _data.size; i++) {
		const uint32 size = _data.registered_sellers[i];
		const uint32 mean = _data.mean[i];
		bid_data[i].Initialize(size);
		for (uint32 j = 0; j < size; j++) {
			bid_data[i].price[j] = mean;
		}
	}
}

// Sort descending.
void ComodityManager::SortBuyData(BidData& buy_data, uint32 left, uint32 right) const
{
	uint32 i = left, j = right;
	const uint32 pivot = buy_data.price[(left + right) / 2];

	while (i <= j) {
		while (buy_data.price[i] > pivot) {
			i++;
		}
		while (buy_data.price[j] < pivot) {
			j--;
		}
		if (i <= j) {
			const Entity entity = buy_data.entity[i];
			const float quantity = buy_data.quantity[i];
			const float quantity_traded = buy_data.quantity_traded[i];
			const uint32 price = buy_data.price[i];
			const uint32 clearing_price = buy_data.clearing_price[i];
			const uint32 storage_index = buy_data.storage_index[i];

			buy_data.entity[i] = buy_data.entity[j];
			buy_data.quantity[i] = buy_data.quantity[j];
			buy_data.quantity_traded[i] = buy_data.quantity_traded[j];
			buy_data.price[i] = buy_data.price[j];
			buy_data.clearing_price[i] = buy_data.clearing_price[j];
			buy_data.storage_index[i] = buy_data.storage_index[j];

			buy_data.entity[j] = entity;
			buy_data.quantity[j] = quantity;
			buy_data.quantity_traded[j] = quantity_traded;
			buy_data.price[j] = price;
			buy_data.clearing_price[j] = clearing_price;
			buy_data.storage_index[j] = storage_index;

			i++;
			j--;
		}
	}

	if (left > j) {
		SortBuyData(buy_data, left, j);
	}
	if (i > right) {
		SortBuyData(buy_data, i, right);
	}
}

// Sort ascending.
void ComodityManager::SortSellData(BidData& sell_data, uint32 left, uint32 right) const
{
	uint32 i = left, j = right;
	const uint32 pivot = sell_data.price[(left + right) / 2];

	while (i <= j) {
		while (sell_data.price[i] < pivot) {
			i++;
		}
		while (sell_data.price[j] > pivot) {
			j--;
		}
		if (i <= j) {
			const Entity entity = sell_data.entity[i];
			const float quantity = sell_data.quantity[i];
			const float quantity_traded = sell_data.quantity_traded[i];
			const uint32 price = sell_data.price[i];
			const uint32 clearing_price = sell_data.clearing_price[i];
			const uint32 storage_index = sell_data.storage_index[i];

			sell_data.entity[i] = sell_data.entity[j];
			sell_data.quantity[i] = sell_data.quantity[j];
			sell_data.quantity_traded[i] = sell_data.quantity_traded[j];
			sell_data.price[i] = sell_data.price[j];
			sell_data.clearing_price[i] = sell_data.clearing_price[j];
			sell_data.storage_index[i] = sell_data.storage_index[j];

			sell_data.entity[j] = entity;
			sell_data.quantity[j] = quantity;
			sell_data.quantity_traded[j] = quantity_traded;
			sell_data.price[j] = price;
			sell_data.clearing_price[j] = clearing_price;
			sell_data.storage_index[j] = storage_index;

			i++;
			j--;
		}
	}

	if (left < j) {
		SortSellData(sell_data, left, j);
	}
	if (i < right) {
		SortSellData(sell_data, i, right);
	}
}

void ComodityManager::ReCalculateSupply(const BidData* const sell_data)
{
	for (uint32 i = 0; i < _data.size; i++) {
		_data.supply[i] = 0;
		const BidData& bd = sell_data[i];

		for (uint32 j = 0; j < bd.size; j++) {
			_data.supply[i] += bd.quantity[j];
		}
	}
}

void ComodityManager::ReCalculateDemand(const BidData* const buy_data)
{
	for (uint32 i = 0; i < _data.size; i++) {
		_data.demand[i] = 0;
		const BidData& bd = buy_data[i];

		for (uint32 j = 0; j < bd.size; j++) {
			_data.demand[i] += bd.quantity[j];
		}
	}
}

void ComodityManager::ResolveSales(BidData* buy_data, BidData* sell_data)
{
	float* average_prices = new float[_data.size];
	uint32* trade_count = new uint32[_data.size];
	memset(average_prices, 0, _data.size * sizeof(float));
	memset(trade_count, 0, _data.size * sizeof(uint32));

	for (uint32 i = 0; i < _data.size; i++) {
		uint32 buy_index = 0, sell_index = 0;
		BidData& bd = buy_data[i];
		BidData& sd = sell_data[i];

		// Instead of creating a new struct and passing it off we re-purpose the bid data
		// struct to contain the results of the market turn.
		// We can average prices when multiple buys complete one sell or vice verca.
		while (buy_index < bd.size && sell_index < sd.size) {
			float quantity_traded = (bd.quantity[buy_index] > sd.quantity[sell_index]) ? sd.quantity[sell_index] : bd.quantity[buy_index];
			uint32 clearing_price = (bd.price[buy_index] + sd.price[sell_index]) / 2;

			bd.quantity[buy_index] -= quantity_traded;
			bd.quantity_traded[buy_index] += quantity_traded;
			bd.clearing_price[buy_index] += clearing_price * quantity_traded;

			sd.quantity[sell_index] -= quantity_traded;
			sd.quantity_traded[sell_index] += quantity_traded;
			sd.clearing_price[sell_index] += clearing_price * quantity_traded;

			average_prices[i] += clearing_price;
			trade_count[i]++;

			if (bd.quantity[buy_index] < 0.1) {
				buy_index++;
			}
			if (sd.quantity[sell_index] < 0.1) {
				buy_index++;
			}
		}
	}

	// recalculate the moving averages.
	for (uint32 i = 0; i < _data.size; i++) {
		const uint32 avg_price = average_prices[i] / trade_count[i];
		_data.previuos_turns[i].Insert(avg_price);
		_data.mean[i] = _data.previuos_turns[i].Average();
	}

	delete[] trade_count;
	delete[] average_prices;
}

const uint32 ComodityManager::DetermineFactoryType()
{
	// first check to see if there are any comodities with non zero demand and zero supply.
	// for now we'll be satisfied with the first one we find.
	uint32 resource_hash = 0;
	for (uint32 i = 0; i < _data.size; i++) {
		if (!_data.supply[i] && _data.demand[i]) {
			resource_hash = _data.comodity_hash[i];
			break;
		}
	}

	// otherwise only create for the currently most profitable type. - moving avg. 8-15 rounds.
	if (!resource_hash) {
		uint32 lowest_price = _data.mean[0];
		for (uint32 i = 1; i < _data.size; i++) {
			if (_data.mean[i] < lowest_price) {
				lowest_price = _data.mean[i];
				resource_hash = _data.comodity_hash[i];
			}
		}
	}

	return resource_hash;
}

void ComodityManager::RegisterBuyers(const OpenMap<uint32, uint32>& register_buyers)
{
	for (uint32 i = 0; i < register_buyers.size; i++) {
		const uint32 comodity_index = register_buyers.key[i];
		_data.registered_buyers[comodity_index] += register_buyers.data[i];
	}
}

void ComodityManager::RegisterSellers(const OpenMap<uint32, uint32>& register_sellers)
{
	for (uint32 i = 0; i < register_sellers.size; i++) {
		const uint32 comodity_index = register_sellers.key[i];
		_data.registered_buyers[comodity_index] += register_sellers.data[i];
	}
}

void ComodityManager::DeregisterBuyers(const OpenMap<uint32, uint32>& defunct_buyers)
{
	for (uint32 i = 0; i < defunct_buyers.size; i++) {
		const uint32 comodity_index = defunct_buyers.key[i];
		_data.registered_buyers[comodity_index] -= defunct_buyers.data[i];
	}
}

void ComodityManager::DeregisterSellers(const OpenMap<uint32, uint32>& defunct_sellers)
{
	for (uint32 i = 0; i < defunct_sellers.size; i++) {
		const uint32 comodity_index = defunct_sellers.key[i];
		_data.registered_sellers[comodity_index] -= defunct_sellers.data[i];
	}
}